In [1]:
import pandas as pd
import warnings
import plotly.express as px
warnings.filterwarnings("ignore")
import statsmodels.api as sm
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np

Chargement des données¶

In [2]:
pd.set_option('display.max_columns', None)



# Import
train = pd.read_csv("train.csv")
test = pd.read_csv("test.csv")
stores = pd.read_csv("stores.csv")
transactions = pd.read_csv("transactions.csv").sort_values(["store_nbr", "date"])


# Datetime
train["date"] = pd.to_datetime(train.date)
test["date"] = pd.to_datetime(test.date)
transactions["date"] = pd.to_datetime(transactions.date)

# Data types
train.onpromotion = train.onpromotion.astype("float16")
train.sales = train.sales.astype("float32")
stores.cluster = stores.cluster.astype("int8")

train.head()
Out[2]:
id date store_nbr family sales onpromotion
0 0 2013-01-01 1 AUTOMOTIVE 0.0 0.0
1 1 2013-01-01 1 BABY CARE 0.0 0.0
2 2 2013-01-01 1 BEAUTY 0.0 0.0
3 3 2013-01-01 1 BEVERAGES 0.0 0.0
4 4 2013-01-01 1 BOOKS 0.0 0.0
In [3]:
transactions.head()
Out[3]:
date store_nbr transactions
1 2013-01-02 1 2111
47 2013-01-03 1 1833
93 2013-01-04 1 1863
139 2013-01-05 1 1509
185 2013-01-06 1 520
In [4]:
stores.head()
Out[4]:
store_nbr city state type cluster
0 1 Quito Pichincha D 13
1 2 Quito Pichincha D 13
2 3 Quito Pichincha D 8
3 4 Quito Pichincha D 9
4 5 Santo Domingo Santo Domingo de los Tsachilas D 4
In [5]:
def describe_data(data, name):
    print(f"\nRésumé statistique de {name}:\n")
    print(data.describe())
    print(f"\nValeurs manquantes dans {name}:\n")
    print(data.isnull().sum())

Résumé statistique des datasets¶

Pour mieux comprendre les distributions des variables et détecter d’éventuelles valeurs manquantes, nous analysons les statistiques descriptives pour chaque dataset.

In [6]:
describe_data(train, "Train")
describe_data(test, "Test")
describe_data(stores, "Stores")
describe_data(transactions, "Transactions")
Résumé statistique de Train:

                 id                           date     store_nbr  \
count  3.000888e+06                        3000888  3.000888e+06   
mean   1.500444e+06  2015-04-24 08:27:04.703088384  2.750000e+01   
min    0.000000e+00            2013-01-01 00:00:00  1.000000e+00   
25%    7.502218e+05            2014-02-26 18:00:00  1.400000e+01   
50%    1.500444e+06            2015-04-24 12:00:00  2.750000e+01   
75%    2.250665e+06            2016-06-19 06:00:00  4.100000e+01   
max    3.000887e+06            2017-08-15 00:00:00  5.400000e+01   
std    8.662819e+05                            NaN  1.558579e+01   

              sales  onpromotion  
count  3.000888e+06    3000888.0  
mean   3.577758e+02          NaN  
min    0.000000e+00          0.0  
25%    0.000000e+00          0.0  
50%    1.100000e+01          0.0  
75%    1.958473e+02          0.0  
max    1.247170e+05        741.0  
std    1.092778e+03          NaN  

Valeurs manquantes dans Train:

id             0
date           0
store_nbr      0
family         0
sales          0
onpromotion    0
dtype: int64

Résumé statistique de Test:

                 id                 date     store_nbr   onpromotion
count  2.851200e+04                28512  28512.000000  28512.000000
mean   3.015144e+06  2017-08-23 12:00:00     27.500000      6.965383
min    3.000888e+06  2017-08-16 00:00:00      1.000000      0.000000
25%    3.008016e+06  2017-08-19 18:00:00     14.000000      0.000000
50%    3.015144e+06  2017-08-23 12:00:00     27.500000      0.000000
75%    3.022271e+06  2017-08-27 06:00:00     41.000000      6.000000
max    3.029399e+06  2017-08-31 00:00:00     54.000000    646.000000
std    8.230850e+03                  NaN     15.586057     20.683952

Valeurs manquantes dans Test:

id             0
date           0
store_nbr      0
family         0
onpromotion    0
dtype: int64

Résumé statistique de Stores:

       store_nbr    cluster
count  54.000000  54.000000
mean   27.500000   8.481481
std    15.732133   4.693395
min     1.000000   1.000000
25%    14.250000   4.000000
50%    27.500000   8.500000
75%    40.750000  13.000000
max    54.000000  17.000000

Valeurs manquantes dans Stores:

store_nbr    0
city         0
state        0
type         0
cluster      0
dtype: int64

Résumé statistique de Transactions:

                                date     store_nbr  transactions
count                          83488  83488.000000  83488.000000
mean   2015-05-20 16:07:40.866232064     26.939237   1694.602158
min              2013-01-01 00:00:00      1.000000      5.000000
25%              2014-03-27 00:00:00     13.000000   1046.000000
50%              2015-06-08 00:00:00     27.000000   1393.000000
75%              2016-07-14 06:00:00     40.000000   2079.000000
max              2017-08-15 00:00:00     54.000000   8359.000000
std                              NaN     15.608204    963.286644

Valeurs manquantes dans Transactions:

date            0
store_nbr       0
transactions    0
dtype: int64

Nous avons chargé et exploré les données principales. Voici les points importants :

  1. Données sans valeurs manquantes :
    • Tous les fichiers (train, test, stores, transactions) sont complets.
  2. Distributions clés :
    • sales et onpromotion dans train présentent des valeurs significativement différentes selon les magasins et périodes.
    • Les transactions varient fortement en fonction des magasins (store_nbr).

Prochaines étapes :¶

Nous allons effectuer une analyse exploratoire plus détaillée (EDA) pour mieux comprendre les relations entre les variables (sales, onpromotion, transactions, etc.).

Analyse exploratoire des ventes¶

In [7]:
sales_by_date = train.groupby("date").sales.sum().reset_index()

fig1 = px.line(
    sales_by_date,
    x="date",
    y="sales",
    title="Évolution des ventes totales dans le temps",
    labels={"date": "Date", "sales": "Ventes totales"},
    template="plotly_white",
)
fig1.update_traces(line=dict(width=2))
fig1.show()

# Moyennes hebdomadaires
train["day_of_week"] = train["date"].dt.dayofweek
weekly_sales = train.groupby("day_of_week").sales.mean().reset_index()

fig2 = px.bar(
    weekly_sales,
    x="day_of_week",
    y="sales",
    title="Ventes moyennes par jour de la semaine",
    labels={"day_of_week": "Jour de la semaine (Lundi=0, Dimanche=6)", "sales": "Ventes moyennes"},
    template="plotly_white",
    color="sales",
    color_continuous_scale="Blues",
)
fig2.show()

# Moyennes mensuelles
train["month"] = train["date"].dt.month
monthly_sales = train.groupby("month").sales.mean().reset_index()

fig3 = px.bar(
    monthly_sales,
    x="month",
    y="sales",
    title="Ventes moyennes par mois",
    labels={"month": "Mois", "sales": "Ventes moyennes"},
    template="plotly_white",
    color="sales",
    color_continuous_scale="Greens",
)
fig3.show()

Évolution temporelle des ventes¶

Objectif :¶

Analyser les tendances générales des ventes au fil du temps pour détecter :

  • Les hausses et baisses saisonnières.
  • Les pics et creux éventuels.

Observations :¶

Le graphique montre :

  • Une croissance saisonnière régulière dans les ventes.
  • Certains pics significatifs (probablement dus à des promotions ou des jours fériés au vue des périodes durant lesquels ils sont observables).

Ventes moyennes par jour de la semaine¶

Objectif :¶

Explorer si certains jours de la semaine sont plus propices aux ventes.

Observations :¶

  • Les ventes sont plus élevées le week-end (samedi et dimanche).
  • Les jours de semaine ont des ventes relativement uniformes.

Ventes moyennes par mois¶

Objectif :¶

Analyser la distribution des ventes selon les mois de l’année.

Observations :¶

  • Décembre affiche les ventes moyennes les plus élevées, ce qui est cohérent avec la saison des fêtes.
  • Les mois d’été semblent légèrement plus performants que les mois de printemps surement du aux vacances qui sont plus propicent à la consommations.

L'impact des promotions sur les ventes.¶

In [8]:
sales_with_promotion = train.groupby(train["onpromotion"] > 0).sales.mean().reset_index()
sales_with_promotion.columns = ["OnPromotion", "AverageSales"]

# Visualisation des ventes moyennes selon la présence de promotions
fig_sales_promo = px.bar(
    sales_with_promotion,
    x="OnPromotion",
    y="AverageSales",
    title="Ventes moyennes selon la présence de promotions",
    labels={"OnPromotion": "Articles en promotion (Oui/Non)", "AverageSales": "Ventes moyennes"},
    template="plotly_white",
    color="AverageSales",
    color_continuous_scale="Blues",
)
fig_sales_promo.show()

Observations :¶

  • Les ventes sont significativement plus élevées lorsqu'il y a des promotions.
  • Cela montre que les promotions jouent un rôle clé dans la stimulation des ventes.
In [9]:
# Agréger les données par date
daily_data = train.groupby("date")[["sales", "onpromotion"]].sum()

# Calcul de corrélation Spearman après agrégation
daily_corr = daily_data.corr(method="spearman").iloc[0, 1]
print(f"Corrélation Spearman (après agrégation par jour) : {daily_corr:.4f}")
Corrélation Spearman (après agrégation par jour) : 0.7007

Analyse¶

  • La corrélation Spearman calculée (0.7007) est positive et forte, montrant que les jours avec plus de promotions sont généralement associés à des ventes plus élevées.
  • Cette relation reste cohérente avec les observations précédentes sur les impacts des promotions.
  • Les variations quotidiennes dans le nombre d'articles en promotion expliquent en partie les fluctuations des ventes globales.
In [10]:
# Agréger par cluster
cluster_data = train.merge(stores, on="store_nbr", how="left")
cluster_data = cluster_data.groupby("cluster")[["sales", "onpromotion"]].sum()

# Corrélation par cluster
cluster_corr = cluster_data.corr(method="spearman").iloc[0, 1]
print(f"Corrélation Spearman par cluster : {cluster_corr:.4f}")

# Visualisation par cluster
fig_cluster = px.bar(
    cluster_data.reset_index(),
    x="cluster",
    y="sales",
    title="Ventes totales par cluster avec promotions",
    labels={"cluster": "Cluster", "sales": "Ventes totales"},
    template="plotly_white",
    color="onpromotion",
)
fig_cluster.show()
Corrélation Spearman par cluster : 0.7966

Observations :¶

  • La corrélation Spearman au niveau des clusters (0.7966) est encore plus forte que la corrélation globale par jour.
  • Certains clusters montrent des volumes de ventes significativement plus élevés, en particulier les clusters 6, 8 et 14.

Hypothèses possibles :¶

  • Les promotions dans ces clusters pourraient cibler des produits spécifiques ou répondre à des besoins locaux (par exemple, promotions sur des produits saisonniers).
  • Ces clusters pourraient inclure des magasins plus grands ou mieux établis, ce qui expliquerait des volumes de ventes plus élevés.

Évolution temporelle des transactions¶

In [11]:
global_transactions = transactions.groupby("date").transactions.sum().reset_index()

# Visualisation de l'évolution globale des transactions
fig11 = px.line(
    global_transactions,
    x="date",
    y="transactions",
    title="Évolution temporelle globale des transactions",
    labels={"date": "Date", "transactions": "Transactions totales"},
    template="plotly_white",
)
fig11.show()

# Transactions par magasin
store_transactions = transactions.groupby(["date", "store_nbr"]).transactions.sum().reset_index()

# Visualisation des transactions par magasin (interactif)
fig12 = px.line(
    store_transactions,
    x="date",
    y="transactions",
    color="store_nbr",
    title="Évolution temporelle des transactions par magasin",
    labels={"date": "Date", "transactions": "Transactions", "store_nbr": "Magasin"},
    template="plotly_white",
)
fig12.show()

# Transactions par cluster (en supposant que 'cluster' est dans le fichier 'stores')
transactions_with_clusters = transactions.merge(stores, on="store_nbr", how="left")
cluster_transactions = transactions_with_clusters.groupby(["date", "cluster"]).transactions.sum().reset_index()

# Visualisation des transactions par cluster
fig13 = px.line(
    cluster_transactions,
    x="date",
    y="transactions",
    color="cluster",
    title="Évolution temporelle des transactions par cluster",
    labels={"date": "Date", "transactions": "Transactions", "cluster": "Cluster"},
    template="plotly_white",
)
fig13.show()

Transaction global¶

Observations détaillées :¶

  • Les transactions globales suivent une tendance saisonnière nette, avec des pics récurrents autour de la période de fin d'année.
  • Des baisses spécifiques sont observées pendant certaines périodes (par exemple, janvier).
  • Ces fluctuations reflètent probablement le comportement d'achat des consommateurs, influencé par des événements externes (fêtes, jours fériés, promotions).

Hypothèses possibles :¶

  • Les pics observés sont probablement dus à des périodes de soldes ou de fêtes (Noël, Nouvel An, etc.).
  • Les creux correspondent à des périodes de faible activité commerciale, comme après les fêtes ou pendant les vacances scolaires.

Interprétation :¶

Les transactions globales montrent une forte dépendance aux cycles saisonniers.

Transactions par magasin¶

Observations détaillées :¶

  • Les magasins montrent des différences importantes dans leurs volumes de transactions, certaines ayant des pics beaucoup plus élevés.
  • Ces variations sont probablement dues à des facteurs tels que :
    • La taille des magasins.
    • Leur localisation géographique.
    • Le type de clientèle qu'ils desservent.

Transactions par cluster¶

Observations détaillées :¶

  • Les clusters montrent des volumes de transactions distincts, certains étant clairement plus performants (clusters 6, 8 et 14).
  • Cependant, certains clusters présentent des volumes de transactions très proches, comme les clusters 17 et 12 ou 9 et 2. Cela suggère qu’un nouveau clustering pourrait être effectué pour regrouper ces clusters très similaire
  • Les transactions dans ces clusters présentent également des pics saisonniers marqués.

Corrélation entre ventes et transactions¶

In [12]:
# Fusion des transactions avec les données de ventes
temp = pd.merge(train.groupby(["date", "store_nbr"]).sales.sum().reset_index(), transactions, how = "left")

# Corrélation entre les ventes et les transactions
correlation =temp.corr("spearman").sales.loc["transactions"]
print(f"Corrélation entre les ventes et les transactions : {correlation:.4f}")

# Visualisation des transactions et des ventes globales
fig8 = px.scatter(
    temp,
    x="transactions",
    y="sales",
    title="Corrélation entre les ventes et les transactions",
    labels={"transactions": "Transactions", "sales": "Ventes"},
    opacity=0.7,
    template="plotly_white",
)
fig8.show()
Corrélation entre les ventes et les transactions : 0.8175

Résultats :¶

  • La corrélation Spearman globale entre les ventes (sales) et les transactions (transactions) est très forte : 0.8175.
  • Le nuage de points confirme une relation monotone positive entre les deux variables : plus le nombre de transactions augmente, plus les ventes augmentent.

Observations détaillées :¶

Bien que la relation soit forte, une dispersion croissante est visible pour les transactions élevées. Cela peut indiquer des variations dues à :

  • Les types de produits vendus (ex. produits à faible coût mais en grande quantité).
  • Les promotions ciblées sur des produits spécifiques

Corrélation entre ventes et transactions par magasin¶

In [13]:
spearman_by_store = (
    temp.groupby("store_nbr")[["sales", "transactions"]]
    .corr(method="spearman")
    .iloc[0::2, 1]
    .reset_index()
    .rename(columns={"transactions": "spearman_corr"})
)

# Visualisation des corrélations par magasin
fig9 = px.bar(
    spearman_by_store,
    x="store_nbr",
    y="spearman_corr",
    title="Corrélation de Spearman entre ventes et transactions par magasin",
    labels={"store_nbr": "Numéro du magasin", "spearman_corr": "Corrélation de Spearman"},
    template="plotly_white",
    color="spearman_corr",
    color_continuous_scale="Viridis",
)
fig9.show()

Résultats :¶

  • Les corrélations Spearman par magasin varient significativement :
    • Certains magasins ont une corrélation élevée (au-dessus de 0.5).
    • D'autres montrent une corrélation faible voire négative.

Observations détaillées :¶

  • Les magasins avec une faible corrélation pourraient avoir des produits ou des comportements spécifiques (ex. faible panier moyen ou ventes liées à des services non mesurés par les transaction).

Corrélation avec le prix du pétrole¶

In [14]:
# Charger les données du prix du pétrole
oil_data = pd.read_csv("oil.csv")
oil_data["date"] = pd.to_datetime(oil_data["date"])

# Remplacer les zéros par NaN pour éviter des biais
oil_data["dcoilwtico"] = np.where(oil_data["dcoilwtico"] == 0, np.nan, oil_data["dcoilwtico"])

# Interpolation linéaire pour combler les valeurs manquantes
oil_data["dcoilwtico_interpolated"] =oil_data["dcoilwtico"].interpolate(method="linear")

temp = temp.merge(oil_data, on="date", how="left")

# Corrélations par désagrégation
spearman_corr_sales = temp[["sales", "dcoilwtico_interpolated"]].corr(method="spearman").iloc[0, 1]
spearman_corr_transactions = temp[["transactions", "dcoilwtico_interpolated"]].corr(method="spearman").iloc[0, 1]

print(f"Corrélation de Spearman entre ventes et prix du pétrole (désagrégé) : {spearman_corr_sales:.4f}")
print(f"Corrélation de Spearman entre transactions et prix du pétrole (désagrégé) : {spearman_corr_transactions:.4f}")

# Graphiques de dispersion
fig, axes = plt.subplots(1, 2, figsize=(15, 5))

# Dispersion : Transactions vs. Prix du pétrole
temp.plot.scatter(x="dcoilwtico_interpolated", y="transactions", ax=axes[0])
axes[0].set_title(f"Prix du pétrole et Transactions (Spearman: {spearman_corr_transactions:.4f})")

# Dispersion : Ventes vs. Prix du pétrole
temp.plot.scatter(x="dcoilwtico_interpolated", y="sales", ax=axes[1], color="r")
axes[1].set_title(f"Prix du pétrole et Ventes (Spearman: {spearman_corr_sales:.4f})")

plt.tight_layout()
plt.show()
Corrélation de Spearman entre ventes et prix du pétrole (désagrégé) : -0.3176
Corrélation de Spearman entre transactions et prix du pétrole (désagrégé) : 0.0380
No description has been provided for this image

Résultats :¶

  • La corrélation Spearman entre les ventes (sales) et le prix du pétrole (dcoilwtico_interpolated) est faible et négative : -0.3176.
  • La corrélation Spearman entre les transactions et le prix du pétrole est très faible et positive : 0.0380.

Observations détaillées :¶

  • Le prix du pétrole semble peu corrélé aux ventes ou aux transactions globales.
  • Une faible corrélation négative pour les ventes pourrait indiquer un lien indirect, comme une baisse de la consommation en période de hausse des prix énergétiques.
  • La corrélation positive avec les transactions est probablement due à des variations saisonnières communes (ex. hausse du trafic pendant les vacances).

Les nuages de points montrent une grande dispersion, confirmant l'absence de relation linéaire forte entre le prix du pétrole et les variables internes.

Impact des jours fériés sur les ventes¶

In [15]:
# Charger les données des jours fériés
holidays = pd.read_csv("holidays_events.csv")
holidays["date"] = pd.to_datetime(holidays["date"])

# Ajouter une colonne binaire pour indiquer les jours fériés
holidays["is_holiday"] = 1  # Marquer tous les jours dans holidays comme fériés

# Fusionner avec les données de ventes
sales_with_holidays = train.merge(holidays[["date", "is_holiday"]], on="date", how="left")
sales_with_holidays["is_holiday"] = sales_with_holidays["is_holiday"].fillna(0)  # Les jours non fériés deviennent 0

# Étape 1 : Moyennes des ventes les jours fériés et normaux
avg_sales_holidays = sales_with_holidays.groupby("is_holiday").sales.mean().reset_index()
print(avg_sales_holidays)

# Étape 2 : Corrélation Spearman entre `is_holiday` et les ventes
holiday_sales_corr = sales_with_holidays[["sales", "is_holiday"]].corr(method="spearman").iloc[0, 1]
print(f"Corrélation Spearman entre les ventes et les jours fériés : {holiday_sales_corr:.4f}")

# Étape 3 : Visualisation des moyennes
import plotly.express as px
fig_holidays = px.bar(
    avg_sales_holidays,
    x="is_holiday",
    y="sales",
    title="Ventes moyennes pendant les jours fériés vs jours normaux",
    labels={"is_holiday": "Jour férié (1=Oui, 0=Non)", "sales": "Ventes moyennes"},
    template="plotly_white",
    color="sales",
    color_continuous_scale="Reds",
)
fig_holidays.show()
   is_holiday       sales
0         0.0  352.159180
1         1.0  393.864777
Corrélation Spearman entre les ventes et les jours fériés : 0.0131

Résultats :¶

  • Les ventes moyennes pendant les jours fériés sont 393.86 contre 352.15 pour les jours normaux.
  • Cela indique une augmentation moyenne des ventes pendant les jours fériés, bien qu'elle reste relativement modeste.
In [16]:
plt.figure(figsize=(15, 6))

# Série temporelle des ventes
plt.plot(sales_with_holidays["date"], sales_with_holidays["sales"], label="Ventes", color="blue", linewidth=1)

# Points pour les jours fériés
holiday_sales = sales_with_holidays[sales_with_holidays["is_holiday"] == 1]
plt.scatter(
    holiday_sales["date"],
    holiday_sales["sales"],
    color="red",
    label="Jours fériés",
    zorder=5,
    s=20,
)

# Personnalisation
plt.title("Série temporelle des ventes avec jours fériés", fontsize=16)
plt.xlabel("Date", fontsize=12)
plt.ylabel("Ventes", fontsize=12)
plt.legend()
plt.grid(visible=True, linestyle="--", alpha=0.6)
plt.tight_layout()

# Affichage
plt.show()
No description has been provided for this image

Résultats :¶

  • Les points rouges indiquent les jours fériés sur la série temporelle des ventes globales.
  • Les jours fériés correspondent parfois à des pics de ventes significatifs, en particulier autour des fêtes de fin d'année (ex. Noël, Nouvel An).

Observations détaillées :¶

  • Les pics de ventes pendant les jours fériés sont visibles, mais pas systématiques.
  • Certains jours fériés ne génèrent pas de hausse notable des ventes, ce qui suggère des variations selon le type de jour férié ou son emplacement dans la semaine.

Hypothèses possibles :¶

Les jours fériés de fin d'année (comme Noël) ont un effet plus fort sur les ventes, car ils sont associés à des traditions d’achat (cadeaux, repas de fête).

In [35]:
### Ventes moyennes pour promotions et jours fériés combinés
In [17]:
# Ajouter une variable combinée promotion + jour férié
sales_with_holidays["promo_holiday"] = (
    (sales_with_holidays["onpromotion"] > 0) & (sales_with_holidays["is_holiday"] == 1)
).astype(int)

# Moyennes des ventes pour chaque combinaison
avg_sales_promo_holiday = sales_with_holidays.groupby("promo_holiday")["sales"].mean().reset_index()

# Visualisation
fig_promo_holiday = px.bar(
    avg_sales_promo_holiday,
    x="promo_holiday",
    y="sales",
    title="Ventes moyennes pour promotions et jours fériés combinés",
    labels={"promo_holiday": "Promotion et jour férié (1=Oui, 0=Non)", "sales": "Ventes moyennes"},
    template="plotly_white",
    color="sales",
    color_continuous_scale="Tealgrn",
)
fig_promo_holiday.show()

Analyse :¶

  • Lorsqu'une promotion coïncide avec un jour férié, les ventes atteignent leur pic. Cette synergie montre que ces deux facteurs combinés amplifient l'engagement des consommateurs.

Ventes moyennes par jour de la semaine et promotions¶

In [18]:
# Assurez-vous que onpromotion est au bon format
sales_with_holidays["onpromotion"] = sales_with_holidays["onpromotion"].astype("float32")

# Ajouter une variable jour de la semaine
sales_with_holidays["weekday"] = sales_with_holidays["date"].dt.weekday

# Moyennes des ventes par jour de la semaine et promotions
avg_sales_weekday_promo = sales_with_holidays.groupby(["weekday", "onpromotion"])["sales"].mean().reset_index()

# Visualisation
fig_weekday_promo = px.bar(
    avg_sales_weekday_promo,
    x="weekday",
    y="sales",
    color="onpromotion",
    barmode="group",
    title="Ventes moyennes par jour de la semaine et promotions",
    labels={"weekday": "Jour de la semaine (0=Lundi, 6=Dimanche)", "sales": "Ventes moyennes", "onpromotion": "Promotions"},
    template="plotly_white",
)
fig_weekday_promo.show()

Analyse :¶

  • Les ventes augmentent chaque jour de la semaine en présence de promotions. Cependant, les week-ends (samedi et dimanche) présentent une demande plus forte, même sans promotion.(barplot2 du notebook)
  • Cela peut refléter des comportements d'achat typiques, où les clients profitent de leur temps libre pour faire leurs courses.

Transactions moyennes pendant les jours fériés vs jours normaux¶

In [29]:
transactions["date"] = pd.to_datetime(transactions["date"])
transactions["store_nbr"] = transactions["store_nbr"].astype(int)

# Fusionner avec les ventes
sales_with_transactions = pd.merge(
    sales_with_holidays, transactions, on=["date", "store_nbr"], how="left"
)

# Vérifiez la colonne transactions
print(sales_with_transactions.columns)


sales_with_transactions["onpromotion"] = (sales_with_transactions["onpromotion"] > 0).astype(int)
Index(['id', 'date', 'store_nbr', 'family', 'sales', 'onpromotion',
       'day_of_week', 'month', 'is_holiday', 'promo_holiday', 'weekday',
       'transactions'],
      dtype='object')
In [30]:
# Moyenne des transactions avec et sans promotions
avg_transactions_promo = sales_with_transactions.groupby("onpromotion")["transactions"].mean().reset_index()

# Visualisation
fig_transactions_promo = px.bar(
    avg_transactions_promo,
    x="onpromotion",
    y="transactions",
    title="Transactions moyennes en fonction des promotions",
    labels={"onpromotion": "Promotions (0=Non, 1=Oui)", "transactions": "Transactions moyennes"},
    template="plotly_white",
    color="transactions",
    color_continuous_scale="Blues",
)
fig_transactions_promo.show()

Analyse :¶

Contrairement aux ventes, l'impact des promotions sur les transactions est relativement faible : on observe une légère augmentation de 1700 à 1750. Cela peut indiquer que les promotions augmentent le montant moyen des achats par transaction, mais n'attirent pas nécessairement plus de clients en termes de fréquence des transactions.

Transactions moyennes pendant les jours fériés vs jours normaux¶

In [31]:
# Moyenne des transactions pendant les jours fériés et jours normaux
avg_transactions_holiday = sales_with_transactions.groupby("is_holiday")["transactions"].mean().reset_index()

# Visualisation
fig_transactions_holiday = px.bar(
    avg_transactions_holiday,
    x="is_holiday",
    y="transactions",
    title="Transactions moyennes pendant les jours fériés vs jours normaux",
    labels={"is_holiday": "Jour férié (0=Non, 1=Oui)", "transactions": "Transactions moyennes"},
    template="plotly_white",
    color="transactions",
    color_continuous_scale="Purples",
)
fig_transactions_holiday.show()

Analyse :¶

L'augmentation des transactions pendant les jours fériés est modérée, passant de 1700 à environ 1750. Cela suggère que les jours fériés augmentent légèrement la fréquentation des magasins, mais pas de manière significative pour les transactions.

Décomposition saisonnière des ventes (Tendance, Saison, Résidus)¶

In [34]:
from statsmodels.tsa.seasonal import seasonal_decompose

# Décomposition des ventes globales
sales_global = sales_with_holidays.groupby("date")["sales"].sum()

# Appliquer la décomposition saisonnière
decomposition = seasonal_decompose(sales_global, model="additive", period=365)

# Visualisation des composantes
fig, axes = plt.subplots(4, 1, figsize=(15, 12), sharex=True)

axes[0].plot(sales_global, label="Ventes réelles", color="blue")
axes[0].set_title("Ventes réelles")
axes[1].plot(decomposition.trend, label="Tendance", color="orange")
axes[1].set_title("Tendance")
axes[2].plot(decomposition.seasonal, label="Saisonnalité", color="green")
axes[2].set_title("Saisonnalité")
axes[3].plot(decomposition.resid, label="Résidus", color="red")
axes[3].set_title("Résidus")

for ax in axes:
    ax.legend()
    ax.grid(True)

plt.tight_layout()
plt.show()
No description has been provided for this image

Analyse :¶

  • Tendance : La tendance montre une augmentation globale des ventes, indiquant une croissance soutenue du marché ou une amélioration continue des performances commerciales.
  • Saison : Les pics saisonniers se répètent à des intervalles réguliers, probablement liés à des périodes comme Noël, les soldes ou les fêtes locales.
  • Résidus : Les résidus indiquent des fluctuations imprévisibles qui ne sont pas capturées par la tendance ou les variations saisonnières. Ces anomalies peuvent être dues à des événements imprévus comme des crises économiques, des promotions exceptionnelles ou des ruptures de stock.

Conclusion Globale et Implications pour la Prédiction des Ventes Globales¶

Résumé des Observations Clés :¶

  1. Saison et Tendance :

    • Les ventes globales présentent une forte composante saisonnière, avec des pics réguliers correspondant à des périodes clés (fin d'année, promotions majeures).
    • Une tendance croissante est également observée sur plusieurs années, confirmant une dynamique ascendante du marché.
  2. Influence des Variables Exogènes :

    • Transactions :
      • Une forte corrélation entre les ventes globales et les transactions ((~0.82)) souligne leur rôle comme prédicteur clé.
      • Les transactions influencent directement les volumes de vente et capturent l’activité commerciale globale.
    • Promotions :
      • Les promotions augmentent significativement les ventes, mais leur effet sur les transactions est limité, suggérant qu’elles agissent directement sur les ventes sans toujours modifier le comportement d’achat.
    • Jours Fériés :
      • Les jours fériés ont un effet positif sur les ventes globales, bien que cet effet soit plus modéré que celui des promotions.
      • Les jours fériés influencent surtout les comportements d'achat à travers des dynamiques locales.
    • Combinaison Promotions + Jours Fériés :
      • Une synergie entre promotions et jours fériés est observée, générant des ventes nettement supérieures aux périodes normales.
    • Jour de la Semaine :
      • Les ventes varient selon les jours de la semaine, avec des volumes plus élevés le week-end, ce qui reflète les habitudes d’achat des consommateurs.
  3. Analyse des Clusters :

    • Les clusters de magasins montrent des performances distinctes, mais certains clusters présentent des similitudes très fortes, comme observé dans les transactions.
    • Cela suggère qu’un reclustering basé sur des caractéristiques supplémentaires (par exemple, via K-means) pourrait être pertinent pour regrouper davantage les clusters proches.
    • Une autre approche intéressante serait de prédire les ventes pour chaque cluster séparément, puis de sommer les résultats pour obtenir une prédiction globale. Cela permettrait de capturer des dynamiques locales et spécifiques à chaque cluster.
  4. Influence Nulle ou Faible :

    • Les corrélations faibles avec le prix du pétrole ((~-0.31)) suggèrent que cette variable n’est pas pertinente pour modéliser les ventes globales.

Implications pour la Modélisation :¶

  1. Modèles Adaptés aux Séries Temporelles :

    • Les caractéristiques temporelles des données justifient l’utilisation de modèles capables de capturer la saisonnalité, les tendances et les cycles, tels que :
      • SARIMAX : Idéal pour intégrer les variables exogènes (transactions, promotions, jours fériés) tout en capturant les composantes temporelles.
      • Prophet avec Régressions Exogènes : Excellente alternative pour modéliser des tendances complexes et des événements spécifiques.
  2. Utilisation des Variables Exogènes :

    • Les variables exogènes pertinentes incluent :
      • Transactions : Corrélation forte avec les ventes.
      • Promotions et Jours Fériés : Impact direct et combiné sur les ventes.
      • Jour de la Semaine : Variabilité intra-semaine importante.
    • Leur intégration est essentielle pour expliquer les variations des ventes globales.
  3. Approches Non Linéaires :

    • Les relations complexes entre promotions, jours fériés, et ventes justifient des modèles non linéaires tels que :
      • XGBoost ou Random Forest : Pour capturer les interactions non linéaires entre les variables.
      • LSTM (Long Short-Term Memory) : Idéal pour les dépendances à long terme dans les séries temporelles, en intégrant les variables exogènes.
  4. Validation et Robustesse :

    • Validation Croisée Temporelle : Indispensable pour évaluer les modèles sur des fenêtres temporelles distinctes et éviter les fuites.
    • Analyse des Résidus : Identifier les structures non expliquées par les modèles pour améliorer leur robustesse.
  5. Clusterisation et Approches Multi-Niveaux :

    • Les analyses exploratoires des clusters suggèrent de tester des modèles spécifiques à chaque cluster, avec une sommation des prédictions pour obtenir une vision globale.
    • L’utilisation de techniques comme K-means pour recluster les magasins ou groupes similaires peut renforcer la précision des modèles en regroupant des magasins aux dynamiques comparables.

Conclusion : Les analyses exploratoires montrent que les variables exogènes comme les transactions, promotions, jours fériés et jour de la semaine sont des prédicteurs robustes des ventes globales. Un modèle temporel intégrant ces variables (comme SARIMAX ou Prophet avec régressions) est bien adapté pour la prédiction. De plus, l’analyse des clusters met en évidence des différences et similarités entre groupes de magasins, justifiant à la fois des reclusterisations (via K-means) et des approches de modélisation spécifiques par cluster. Ces méthodes combinées garantiront des prédictions fiables et exploitables.

In [ ]: